--[[
Name: LibCooldown-Item-2.0
Revision: $REVISION$
Author(s): Sirow (EU-Gul'dan)
Website: http://code.google.com/p/sirow/
Description: Register item cooldowns to more descriptive events.
Dependencies: LibStub, CallbackHandler-1.0, LibSimpleTimer-1.0
]]


-------------------------------------------------------------------------------
-- Library - Version Info & Dependencies
-------------------------------------------------------------------------------
-- Version
local MAJOR = "LibCooldown-Item-2.0"
local MINOR = tonumber(("$Revision: 10 $"):match("(%d+)")) + 90000

-- Dependencies
if not LibStub 						  	then error(MAJOR .. " requires LibStub.") 				end
if not LibStub("LibSimpleTimer-1.0")  	then error(MAJOR .. " requires LibSimpleTimer-1.0.") 	end
if not LibStub("CallbackHandler-1.0") 	then error(MAJOR .. " requires CallbackHandler-1.0.") 	end
if not LibStub("LibBabble-Zone-3.0") 	then error(MAJOR .. " requires LibBabble-Zone-3.0.") 	end


-------------------------------------------------------------------------------
-- Library - Register
-------------------------------------------------------------------------------
-- Register
local LibCooldownItem 	= LibStub:NewLibrary(MAJOR, MINOR)
if not LibCooldownItem then return end

-- Embed CallbackHandler and LibSimpleTimer
LibCooldownItem.events 	= LibCooldownItem.events or LibStub("CallbackHandler-1.0"):New(LibCooldownItem)
local SimpleTimer		= LibStub("LibSimpleTimer-1.0")
local BabbleZone		= LibStub("LibBabble-Zone-3.0"):GetReverseLookupTable()

-- SpeedUpz
local GetContainerItemLink		= GetContainerItemLink
local GetContainerItemCooldown	= GetContainerItemCooldown
local GetContainerNumSlots		= GetContainerNumSlots
local GetInventoryItemLink		= GetInventoryItemLink
local GetInventoryItemCooldown	= GetInventoryItemCooldown
local GetInventorySlotInfo		= GetInventorySlotInfo
local GetItemInfo				= GetItemInfo


-------------------------------------------------------------------------------
-- Library - Variables
-------------------------------------------------------------------------------	
-- Minimal duration for items to track (in seconds)
local minDuration 	= 3
local lastUpdate	= 0
local updateDelay	= 3

-- Search location
SEARCHLOC_EQP	= "equipped"
SEARCHLOC_INV	= "inventory"

-- Event handling
LibCooldownItem.frame = LibCooldownItem.frame or CreateFrame("Frame")
local frame = LibCooldownItem.frame
frame:UnregisterAllEvents()

-- Items in this table get monitored
itemCache 	= {
	[SEARCHLOC_EQP]	= {},
	[SEARCHLOC_INV]	= {},
}
local sheduledItems	= {
	[SEARCHLOC_EQP]	= {},
	[SEARCHLOC_INV]	= {},
}

-- Startup/disable stuff
hasInventoryStartCallback 	= false
hasInventoryStopCallback 	= false
hasEquippedStartCallback 	= false
hasEquippedStopCallback 	= false
noCache						= true

-- Arena, need to reset timer here
local arenas = {
	[BabbleZone["Blade's Edge Arena"]] = true,
	[BabbleZone["Nagrand Arena"]] 		= true,
	[BabbleZone["Ruins of Lordaeron"]] = true,
}


-------------------------------------------------------------------------------
-- Library - Locals
-------------------------------------------------------------------------------
-- Possible slot names (equipment slots)
local slotNames = { 
	"HeadSlot", 
	"NeckSlot", 
	"ShoulderSlot", 
	"BackSlot", 
	"ChestSlot", 
	"ShirtSlot", 
	"TabardSlot", 
	"WristSlot", 
	"HandsSlot", 
	"WaistSlot", 
	"LegsSlot", 
	"FeetSlot", 
	"Finger0Slot",
	"Finger1Slot",  
	"Trinket0Slot", 
	"Trinket1Slot", 
	"MainHandSlot", 
	"SecondaryHandSlot", 
	"RangedSlot", 
	"AmmoSlot",
}


-------------------------------------------------------------------------------
-- Library - Core
-------------------------------------------------------------------------------
local ScanItem_Equipped, ScanItem_Inventory, UpdateItemCooldown, Found_LastEquippedLoc, Found_LastInventoryLoc, ItemFound, RelayEndEvent, CheckZoneReset
do

-- Scan equipment for items which have a cooldown
ScanItem_Equipped = function ()
	-- Pre-loop variables
	local slotName, slotID, hasCooldown, itemName, itemLink
	
	-- View all equipped items
	for _, slotName in pairs(slotNames) do
		-- Get info about item
		slotID 				= GetInventorySlotInfo(slotName)
		itemLink			= GetInventoryItemLink("player", slotID)
		itemName			= GetItemInfo(itemLink or "")
		_, _, hasCooldown 	= GetInventoryItemCooldown("player", slotID)
		
		-- Add if it has a cooldown
		if (hasCooldown  == 1 and itemLink and itemName) then
			-- Create new entry or update old one
			if (itemCache[SEARCHLOC_EQP][slotID]) then
				-- Test if item was equipped to another slot
				if (not Found_LastEquippedLoc(slotID)) then
					-- Reset cooldown timer
					if (SimpleTimer:HasTimer(string.format("LibCD_EquippedItemCooldownShedule_%d", slotID))) then
						SimpleTimer:CancelTimer(string.format("LibCD_EquippedItemCooldownShedule_%d", slotID))
					end
					
					-- Reset cooldown (Tracker)
					sheduledItems[SEARCHLOC_EQP][slotID] = nil
					
					-- Reset cooldown (cache)
					itemCache[SEARCHLOC_EQP][slotID] = {
						startTime 		= 0,
						LastItemName 	= itemName,
					}
				end
			else
				itemCache[SEARCHLOC_EQP][slotID] = {
					LastItemName 	= itemName,
					startTime 		= 0,
				}
			end
		
		-- Reset cooldown
		elseif (itemCache[SEARCHLOC_EQP][slotID]) then
			-- Delete from cache and tracker
			itemCache[SEARCHLOC_EQP][slotID] = nil
			
			-- Delete stop trigger, si nce item isn't equipped anymore
			if (self:HasTimer(string.format("LibCD_EquippedItemCooldownShedule_%d", slotID))) then
				self:CancelTimer(string.format("LibCD_EquippedItemCooldownShedule_%d", slotID))
				
				sheduledItems[SEARCHLOC_EQP][slotID] = nil
			end
		end
	end
end


-- Scan complete inventory for item which have a cooldown
ScanItem_Inventory = function ()
	local bagID, slotID, hasCooldown, itemName, itemLink, itemID
	
	-- View all bag items
	for bagID = 0, 4 do
		for slotID = 1, GetContainerNumSlots(bagID) do
			itemLink			= GetContainerItemLink(bagID, slotID)
			itemName			= GetItemInfo(itemLink or "")
			_, _, hasCooldown 	= GetContainerItemCooldown(bagID, slotID)
			
			-- Item available?
			if (itemLink and itemName) then
				-- Fetch ItemID
				itemID = itemLink:match("item:(%d+):")
				
				-- Item has cooldown?
				if (hasCooldown  == 1) then
					-- Create new entry or update old one
					if (itemCache[SEARCHLOC_INV][itemID]) then
						itemCache[SEARCHLOC_INV][itemID].LastItemName 	= itemName
						itemCache[SEARCHLOC_INV][itemID].LastBagLoc 	= {
							bagID 	= bagID,
							slotID 	= slotID
						}
					else
						itemCache[SEARCHLOC_INV][itemID] = {
							LastBagLoc = {
								bagID = bagID,
								slotID = slotID
							},
							LastItemName 	= itemName,
							startTime 		= 0
						}
					end
				
				-- Nope it doesn't have a cooldown
				elseif (itemCache[SEARCHLOC_INV][itemID]) then
					-- Delete from cache and tracker
					itemCache[SEARCHLOC_INV][itemID] = nil
					
					-- Delete stop trigger, si nce item isn't equipped anymore
					if (self:HasTimer(string.format("LibCD_ItemCooldownShedule_%d", itemID))) then
						self:CancelTimer(string.format("LibCD_ItemCooldownShedule_%d", itemID))
						
						sheduledItems.inventory[itemID] = nil
					end
				end
			end
		end
	end
end


-- Scan (known) equipment-slot for this item (the slot the item USED to be)
Found_LastEquippedLoc = function (slotID)
	-- See if we allready know where the item is located
	if (itemCache[SEARCHLOC_EQP][slotID] and itemCache[SEARCHLOC_EQP][slotID].LastItemName) then
		-- Compare item we are searching for with item on this (known) slot
		if (itemCache[SEARCHLOC_EQP][slotID].LastItemName == GetItemInfo(GetInventoryItemLink("player", slotID))) then
			return true
			
		-- Item is not at the known location anymore
		else
			return false
		end
		
	-- Item location is unknown
	else
		return false
	end
end



-- Scan (known) bag slot for this item (the slot the item USED to be)
Found_LastInventoryLoc = function (itemID)
	-- See if we allready know where the item is located
	if (itemCache[SEARCHLOC_INV][itemID] and itemCache[SEARCHLOC_INV][itemID].LastBagLoc) then
		-- Compare item we are searching for with item on this (known) slot
		if (itemCache[SEARCHLOC_INV][itemID].LastItemName == GetItemInfo(GetContainerItemLink(itemCache[SEARCHLOC_INV][itemID].LastBagLoc.bagID, itemCache[SEARCHLOC_INV][itemID].LastBagLoc.slotID))) then
			return true
			
		-- Item is not at the known location anymore
		else
			return false
		end
		
	-- Item location is unknown
	else
		return false
	end
end


-- Make sure its still the right item and it really has a cooldown
ItemFound = function (searchLoc, itemIDorSlotID)
	-- Test if spell is still at this spellID
	if (searchLoc == SEARCHLOC_EQP and Found_LastEquippedLoc(itemIDorSlotID) or searchLoc == SEARCHLOC_INV and Found_LastInventoryLoc(itemIDorSlotID)) then
		return true
	else
		-- Rescan
		if (searchLoc == SEARCHLOC_EQP) then
			ScanItem_Equipped()
		elseif (searchLoc == SEARCHLOC_INV) then
			ScanItem_Inventory()
		end
		
		-- Spell found or not?
		if (itemCache[SEARCHLOC_INV][itemIDorSlotID]) then
			return true
		else
			return false
		end
	end
end


-- Update all item cooldowns
UpdateItemCooldown = function (searchLoc)
	-- Fetch spell-infos once
	if (noCache) then
		ScanItem_Inventory()
		ScanItem_Equipped()
		
		noCache = false
	end
	
	-- Temp variables
	local startTime, duration, hasCooldown, itemIDorSlotID, itemInfo
	
	-- For each item that should be monitored
	for itemIDorSlotID, itemInfo in pairs(itemCache[searchLoc]) do
		-- Make sure we know were the item is located 
		if (ItemFound(searchLoc, itemIDorSlotID)) then
			-- Scan cooldown of equipped item
			if (searchLoc == SEARCHLOC_EQP) then
				startTime, duration, hasCooldown = GetInventoryItemCooldown("player", itemIDorSlotID)
			-- Scan cooldown of bags item
			elseif (searchLoc == SEARCHLOC_INV) then
				startTime, duration, hasCooldown = GetContainerItemCooldown(itemInfo.LastBagLoc.bagID, itemInfo.LastBagLoc.slotID)
			end
		
			-- Does this item have a cooldown? (Unless someone added item without CD, this should't happen)
			if (hasCooldown == 1 and startTime ~= 0 and duration > minDuration and itemInfo.startTime ~= startTime) then
				-- Update start-time
				itemInfo.startTime = startTime
				
				-- Shedule event with duration and trigger event
				if (searchLoc == SEARCHLOC_EQP and hasEquippedStartCallback) then
					local itemLink	= GetInventoryItemLink("player", itemIDorSlotID)
					local itemID 	= itemLink:match("item:(%d+):")

					LibCooldownItem.events:Fire(
						"LibCooldownEquippedItem_Start", 
						itemID,
						itemInfo.LastItemName,
						startTime,
						duration,
						itemIDorSlotID
					)
				elseif (searchLoc == SEARCHLOC_INV and hasInventoryStartCallback) then
					LibCooldownItem.events:Fire(
						"LibCooldownInventoryItem_Start", 
						itemIDorSlotID,
						itemInfo.LastItemName,
						startTime,
						duration,
						itemInfo.LastBagLoc.bagID,
						itemInfo.LastBagLoc.slotID
					)
				end

				sheduledItems[searchLoc][itemIDorSlotID] = true
				SimpleTimer:ScheduleTimer(
					"LibCooldownItem_Shedule_" .. searchLoc .. "_" .. itemIDorSlotID, 
					RelayEndEvent, 
					startTime - GetTime() + duration, 
					searchLoc, 
					itemIDorSlotID,
					startTime,
					duration
				)
			end
		end
	end
end


-- Make sure firing event is still a valid task
RelayEndEvent = function(searchLoc, itemIDorSlotID, startTime, duration)
	-- Make sure spell is still available
	if (sheduledItems[searchLoc][itemIDorSlotID] and ItemFound(searchLoc, itemIDorSlotID)) then
		local itemInfo	= itemCache[searchLoc][itemIDorSlotID]
		
		-- Trigger event
		if (searchLoc == SEARCHLOC_EQP and hasEquippedStopCallback) then
			local itemLink	= GetInventoryItemLink("player", itemIDorSlotID)
			local itemID 	= itemLink:match("item:(%d+):")
					
			LibCooldownItem.events:Fire(
				"LibCooldownEquippedItem_End", 
				itemID,
				itemInfo.LastItemName,
				startTime,
				duration,
				itemIDorSlotID
			)
		elseif (searchLoc == SEARCHLOC_INV and hasInventoryStopCallback) then
			LibCooldownItem.events:Fire(
				"LibCooldownInventoryItem_End", 
				itemIDorSlotID,
				itemInfo.LastItemName,
				startTime,
				duration,
				itemInfo.LastBagLoc.bagID,
				itemInfo.LastBagLoc.slotID
			)
		end
	end
end


-- Check if player entered arena, so we need to reset all spell cooldowns
CheckZoneReset = function ()
	-- Fetch current zone
	local zone = GetRealZoneText()
	
	-- We'll just have to wait a few seconds
	if (not zone) then
		SimpleTimer:ScheduleTimer(CheckZoneReset, 5)
		
	-- Delete cooldownEnd shedules
	elseif (arenas[zone]) then
		for searchLoc, items in pairs(sheduledItems) do
			-- Cancel all Timers
			for itemIDorSlotID, _ in pairs(items) do
				SimpleTimer:CancelTimer("LibCooldownItem_Shedule_" .. searchLoc .. "_" .. itemIDorSlotID)
			end
			
			-- Remove from watch-list
			sheduledItems[searchLoc] = {}
		end
	end
end

end


-------------------------------------------------------------------------------
-- Library - Event Handling
-------------------------------------------------------------------------------
-- Make sure to only register events when someone is listening (someone is listening now)
function LibCooldownItem.events:OnUsed(target, eventName)
	-- Register required events
	if (eventName == "LibCooldownEquippedItem_Start" 	or eventName == "LibCooldownEquippedItem_End" or 
		eventName == "LibCooldownInventoryItem_Start" 	or eventName == "LibCooldownInventoryItem_End") then		
		frame:SetScript("OnEvent", function ()
			if (event == "BAG_UPDATE_COOLDOWN") then
				-- Cooldowns by itemName/itemID
				UpdateItemCooldown(SEARCHLOC_EQP)
				UpdateItemCooldown(SEARCHLOC_INV)
			elseif (event == "UNIT_INVENTORY_CHANGED" and unit == "player" or event == "BAG_UPDATE") then
				if (not SimpleTimer:IsTimerScheduled("LibCooldownItem_Shedule_Scan_" .. SEARCHLOC_EQP)) then
					if (GetTime() > lastUpdate + updateDelay) then
						ScanItem_Inventory()
						ScanItem_Equipped()
					else
						lastUpdate = GetTime()
						
						SimpleTimer:ScheduleTimer(
							"LibCooldownItem_Shedule_Scan_" .. SEARCHLOC_EQP, 
							ScanItem_Equipped, 
							updateDelay
						)
						SimpleTimer:ScheduleTimer(
							"LibCooldownItem_Shedule_Scan_" .. SEARCHLOC_INV, 
							ScanItem_Inventory, 
							updateDelay
						)
					end
				end
			elseif(event == "ZONE_CHANGED_NEW_AREA") then
				CheckZoneReset()
			end
		end)
		frame:RegisterEvent("BAG_UPDATE_COOLDOWN")
		frame:RegisterEvent("UNIT_INVENTORY_CHANGED")
		frame:RegisterEvent("BAG_UPDATE")
		frame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
	end
	
	if (eventName == "LibCooldownInventoryItem_Start") then
		hasInventoryStartCallback = true
	elseif (eventName == "LibCooldownInventoryItem_End") then
		hasInventoryStopCallback = true
	elseif (eventName == "LibCooldownEquippedItem_Start") then
		hasEquippedStartCallback = true
	elseif (eventName == "LibCooldownEquippedItem_End") then
		hasEquippedStopCallback = true
	end
end


-- Make sure to only register events when someone is listening (no one is listening now)
function LibCooldownItem.events:OnUnused(target, eventName)
	if (eventName == "LibCooldownInventoryItem_Start") then
		hasInventoryStartCallback = false
	elseif (eventName == "LibCooldownInventoryItem_End") then
		hasInventoryStopCallback = false
	elseif (eventName == "LibCooldownEquippedItem_Start") then
		hasEquippedStartCallback = false
	elseif (eventName == "LibCooldownEquippedItem_End") then
		hasEquippedStopCallback = false
	end
	
	-- Unregister events
	if (not hasInventoryStartCallback 	and not hasInventoryStopCallback and
		not hasEquippedStartCallback 	and not hasEquippedStopCallback) then
		frame:SetScript("OnEvent", nil)
		frame:UnregisterEvent("BAG_UPDATE_COOLDOWN")
		frame:UnregisterEvent("UNIT_INVENTORY_CHANGED")
		frame:UnregisterEvent("BAG_UPDATE")
		frame:UnregisterEvent("ZONE_CHANGED_NEW_AREA")
	end
end


-------------------------------------------------------------------------------
-- Library - Documentation
-------------------------------------------------------------------------------	
--[[

Events fired:
	"LibCooldownInventoryItem_Start" (
		itemID,				= ItemID of triggered item
		itemName				= Name of triggered item
		startTime,				= Time when cooldown started (as returned by GetTime())
		duration				= Duration of cooldown until finished
		bagID				= Bag ID as used by GetContainerItemCooldown(bagID, slotID)
		slotID				= Bag slot ID as used by GetContainerItemCooldown(bagID, slotID)
	)
	
	"LibCooldownEquippedItem_Start" (
		itemID,				= ItemID of triggered item
		itemName				= Name of triggered item
		startTime,				= Time when cooldown started (as returned by GetTime())
		duration				= Duration of cooldown until finished
		slotID				= Equipment slot as used by GetInventoryItemCooldown("player", slotID)
	)
	
	"LibCooldownInventoryItem_End" (
		itemID,				= ItemID of triggered item
		itemName				= Name of triggered item
		startTime,				= Time when cooldown started (as returned by GetTime())
		duration				= Duration of cooldown until finished
		bagID				= Bag ID as used by GetContainerItemCooldown(bagID, slotID)
		slotID				= Bag slot ID as used by GetContainerItemCooldown(bagID, slotID)
	)

	"LibCooldownEquippedItem_End" (
		itemID,				= ItemID of triggered item
		itemName				= Name of triggered item
		startTime,				= Time when cooldown started (as returned by GetTime())
		duration				= Duration of cooldown until finished
		slotID				= Equipment slot as used by GetInventoryItemCooldown("player", slotID)
	)
	
Register Events:
	CDS = LibStub:GetLibrary("LibCooldown-Spell-2.0")
	CDS:RegisterCallback(
		"EVENTNAME", 
		CALLBACKFUNTON
	)
	
EOF]]--